-- LB_KILL系列的Utility
-- local LB_U = require "packages.lb_kill_utility.utility"

---@class Utility_LB_Kill
local Utility = require 'packages.lb_kill_utility._base'

local U = require "packages.utility.utility"

-- 用于多选的 interaction type ，修改自askToChoices，
-- 限制"最少选择项数"需要在函数（如feasible）中判断(self.interaction.data and #self.interaction.data > min_num)
--
-- 可以赋值的属性有：
-- * choices: string[]，可选选项
-- * default: string[]，默认选项，默认为空
-- * detailed: bool，为真的话送详细信息
-- * all_choices: string[] 类型，保存着所有选项，会被前端翻译
-- * max_num: integer，最多选择项数，至多为#choices，默认为1
Utility.CheckBox = function(spec)
  spec.choices = type(spec.choices) == "table" and spec.choices or Util.DummyTable
  spec.all_choices = type(spec.all_choices) == "table" and spec.all_choices or spec.choices
  spec.default_choice = spec.default_choice or {}
  spec.detailed = spec.detailed or false
  spec.type = "custom"
  spec.max_num = spec.max_num and math.min(spec.max_num, #spec.choices) or 1
  spec.qml_path = "packages/lb_kill_utility/qml/SkillCheckBox"
  return spec
end

--- 角色是否为某势力（检索kingdom_table）
---@param player ServerPlayer @ 角色
---@param kingdom string @ 检索势力
Utility.CompareKingdomWith = function(player, kingdom)
  if player.kingdom == kingdom then return true end
  local general = Fk.generals[player.general] -- 只判定主将
  if general and type(general.kingdom_table) == "table" then
    return table.contains(general.kingdom_table, kingdom)
  end
  return false
end

--- 重铸一名角色的牌为某牌，从牌堆或弃牌堆检索等量张符合规则的牌（可能不够），角色死亡或没检索到返回空表。
--- （此方法可以获得【影】，如果规则包含【影】且牌数不够，用【影】补足）
---@param card_ids integer[] @ 被重铸的牌
---@param who ServerPlayer @ 重铸的角色
---@param skillName? string @ 技能名，默认为“重铸”
---@param pattern? string @ 检索牌的规则
---@param skip? boolean @ 是否跳过获得牌，仅返回检索到的牌
---@param moveMark? table|string @ 移动后自动赋予标记，格式：{标记名(支持-inarea后缀，移出值代表区域后清除), 值}
---@return integer[] @ 摸到的牌
Utility.CertainlyRecastCard = function(card_ids, who, skillName, pattern, skip, moveMark)
  local room = who.room
  if type(card_ids) == "number" then
    card_ids = { card_ids }
  end
  skillName = skillName or "recast"
  room:moveCards({
    ids = card_ids,
    from = who,
    toArea = Card.DiscardPile,
    skillName = skillName,
    moveReason = fk.ReasonRecast,
    proposer = who,
  })
  room:sendFootnote(card_ids, {
    type = "##RecastCard",
    from = who.id,
  })
  room:broadcastPlaySound("./audio/system/recast")
  room:sendLog {
    type = skillName == "recast" and "#Recast" or "#RecastBySkill",
    from = who.id,
    card = card_ids,
    arg = skillName,
  }
  if who.dead then return {} end
  pattern = pattern or "."
  local ids = {}
  if pattern == "shade" then
    ids = U.getShade(room, #card_ids)
  else
    ids = room:getCardsFromPileByRule(pattern, #card_ids)
    if Exppattern:Parse(pattern):matchExp("shade") and #ids < #card_ids then
      table.insertTable(ids, U.getShade(room, #card_ids - #ids))
    end
  end
  if #ids > 0 and not skip then
    room:obtainCard(who, ids, true, fk.ReasonJustMove, who, skillName, moveMark)
  end
  return ids
end

--- 令一名角色将牌数调整至某值（多弃少摸）
---@param who ServerPlayer @ 调整的角色
---@param skillName? string @ 技能名
---@param balance integer @ 要调整至几
---@param include_equip? boolean @是否计入装备区
---@param skip? boolean @ 是否跳过摸或弃牌，仅返回要弃置的牌
---@param moveMark? table|string @ 移动后自动赋予标记，格式：{标记名(支持-inarea后缀，移出值代表区域后清除), 值}
---@return table @ draw摸到的牌，dis弃置的牌
Utility.BalanceCards = function(who, skillName, balance, include_equip, skip, moveMark)
  local room = who.room
  skillName = skillName or "game_rule"
  local cards_num = #who:getCardIds(include_equip and "he" or "h")
  local num = balance - cards_num
  if who.dead or cards_num == balance then return {} end
  local list = {}
  if num > 0 then
    if not skip then
      list.draw = who:drawCards(num, skillName, nil, moveMark)
    end
  elseif num < 0 then
    num = math.abs(num)
    list.dis = room:askToDiscard(who, {
      min_num = num,
      max_num = num,
      skill_name = skillName,
      cancelable = false,
      include_equip = include_equip,
      skip = skip,
    })
  end
  return list
end

--- 令一名隐匿者登场，仅以主将调整属性
---@param player ServerPlayer @ 登场的玩家
---@param reason? string @ 登场原因，一般为技能名（泰始）、扣减体力（"HpChanged"）、回合开始（"TurnStart"）
---@return boolean @ 返回是否成功登场
Utility.GeneralAppear = function(player, reason)
  local room = player.room
  local data = {
    m = player:getMark("__hidden_general"),
    d = player:getMark("__hidden_deputy"),
    reason = reason or "game_rule",
  }
  room.logic:trigger(U.BeforeGeneralAppeared, player, data)
  if data.prevented then
    return false
  end
  if Fk.generals[data.m] then
    player.general = data.m
    room:sendLog {
      type = "#RevealGeneral",
      from = player.id,
      arg = "mainGeneral",
      arg2 = data.m,
    }
  else
    data.m = nil
  end
  if Fk.generals[data.d] then
    player.deputyGeneral = data.d
    room:sendLog {
      type = "#RevealGeneral",
      from = player.id,
      arg = "deputyGeneral",
      arg2 = data.d,
    }
  else
    data.d = nil
  end
  if data.m == nil and data.d == nil then
    return false
  end

  for _, skill in ipairs(player.player_skills) do
    if skill.name:endsWith("hidden_skill&") then
      room:handleAddLoseSkills(player, "-" .. skill.name, nil, false, true)
    end
  end
  room:setPlayerMark(player, "__hidden_general", 0)
  room:setPlayerMark(player, "__hidden_deputy", 0)
  local general = Fk.generals[player.general]
  local deputy = Fk.generals[player.deputyGeneral]
  player.gender = general.gender
  player.kingdom = general.kingdom
  room:broadcastProperty(player, "gender")
  room:broadcastProperty(player, "general")
  room:broadcastProperty(player, "deputyGeneral")
  room:askToChooseKingdom({ player })
  room:broadcastProperty(player, "kingdom")

  if player:getMark("__hidden_record") ~= 0 then
    player.maxHp = player:getMark("__hidden_record").maxHp
    player.hp = player:getMark("__hidden_record").hp
  else
    player.maxHp = player:getGeneralMaxHp()
    player.hp = deputy and math.floor((deputy.hp + general.hp) / 2) or general.hp
  end
  player.shield = math.min(general.shield + (deputy and deputy.shield or 0), 5)
  if player:getMark("__hidden_record") ~= 0 then
    room:setPlayerMark(player, "__hidden_record", 0)
  end
  local FirstAppear = room:getBanner("FirstAppear") or {}
  if not FirstAppear[tostring(player.id)] then -- 记录首次登场，用于确认初始属性
    FirstAppear[tostring(player.id)] = true
    room:setBanner("FirstAppear", FirstAppear)
    local changer = Fk.game_modes[room:getSettings('gameMode')]:getAdjustedProperty(player)
    if changer then
      for key, value in pairs(changer) do
        player[key] = value
      end
    end
  end
  room:broadcastProperty(player, "maxHp")
  room:broadcastProperty(player, "hp")
  room:broadcastProperty(player, "shield")

  local lordBuff = player.role == "lord" and player.role_shown == true and room:isGameMode("role_mode")
  local skills = general:getSkillNameList(lordBuff)
  if deputy then
    table.insertTable(skills, deputy:getSkillNameList(lordBuff))
  end
  skills = table.filter(skills, function(s)
    local skill = Fk.skills[s]
    return skill and
        (#skill.skeleton.attached_kingdom == 0 or table.contains(skill.skeleton.attached_kingdom, player.kingdom))
  end)
  if #skills > 0 then
    room:handleAddLoseSkills(player, table.concat(skills, "|"), nil, false)
  end

  room.logic:trigger(U.GeneralAppeared, player, data)
end


--- 进入隐匿状态（身份局的开局隐匿在游戏流程中，此函数适用于diy的主动进入隐匿状态）
---@param player ServerPlayer @ 目标角色
---@param general string? @ 用于隐匿的武将名
Utility.enterHidden = function(player, general)
  local room = player.room
  room:sendLog({
    type = "#EnterHidden",
    from = player.id,
  })
  local skills = "hidden_skill&"
  if general and Fk.generals[general] then
    skills = ""
    for _, s in ipairs(Fk.generals[general]:getSkillNameList(true)) do
      skills = skills .. "|" .. s
    end
  end
  room:setPlayerMark(player, "__hidden_general", player.general)
  for _, s in ipairs(Fk.generals[player.general]:getSkillNameList(true)) do
    if player:hasSkill(s, true) then
      skills = skills .. "|-" .. s
    end
  end
  if player.deputyGeneral ~= "" then
    room:setPlayerMark(player, "__hidden_deputy", player.deputyGeneral)
    for _, s in ipairs(Fk.generals[player.deputyGeneral]:getSkillNameList(true)) do
      if player:hasSkill(s, true) then
        skills = skills .. "|-" .. s
      end
    end
  end
  player.general = general or "hiddenone"
  player.gender = General.Male
  room:broadcastProperty(player, "gender")
  if player.deputyGeneral ~= "" then
    player.deputyGeneral = ""
  end
  player.kingdom = general and Fk.generals[general].kingdom or "jin"
  room:setPlayerMark(player, "__hidden_record",
    {
      maxHp = player.maxHp,
      hp = player.hp,
    })
  player.maxHp = 1
  player.hp = 1
  for _, property in ipairs({ "general", "deputyGeneral", "kingdom", "maxHp", "hp" }) do
    room:broadcastProperty(player, property)
  end
  room:handleAddLoseSkills(player, skills, nil, false, true)
end
Fk:loadTranslationTable {
  ["#EnterHidden"] = "%from 进入隐匿状态",
  ["enter_hidden_href"] = "记录当前的体力上限和体力值，失去当前武将牌上的所有技能，将武将替换为隐匿将。再次登场后，" ..
      "将体力上限和体力值调整为记录值，武将替换回隐匿前的武将，获得武将牌上所有的技能。",
}


---------------------------------------------------------------------------------------------------

--[[
  冻结体力值

  体力值大于0且未处于冻结状态的角色可以被冻结体力值。
  “体力值”冻结状态：
  1.角色的体力值或体力上限变化时，解除冻结状态并防止之。
  2.角色受到火焰伤害时，解除冻结状态并令此伤害+1。

--]]

--拥有此标记的角色冻结体力值。可带"-phase"、"-turn"等suffix
MarkEnum.FreezeHp = "@@FreezeHp"


Fk:loadTranslationTable {
  ["#freeze_href"] = "#\"<b>冻结体力值</b>\"：体力值大于0且未处于冻结状态的角色可以被冻结体力值。<br>" ..
      "“体力值”冻结状态：<br>" ..
      "1.角色的体力值或体力上限变化时，解除冻结状态并防止之；<br>" ..
      "2.角色受到火焰伤害时，解除冻结状态并令此伤害+1。",

  ["#Freeze_skill_log"] = "%from 冻结了体力值",
  ["#UnFreeze_skill_log"] = "%from 解除了冻结状态",
}


-- 体力值是否冻结
---@param player Player @ 目标角色
Utility.isfreezeHp = function(player)
  return player:getMark(MarkEnum.FreezeHp) > 0 or table.find(MarkEnum.TempMarkSuffix, function(suffix)
    return player:getMark(MarkEnum.FreezeHp .. suffix) > 0
  end)
end

-- 冻结体力值
---@param player ServerPlayer
---@param scope? integer @ 冻结的历史范围，默认永久
Utility.freezeHp = function(player, scope)
  local room = player.room
  room:addSkill("#Freeze_skill")
  local suffix_mapper = {
    [Player.HistoryGame] = "",
    [Player.HistoryRound] = "-round",
    [Player.HistoryTurn] = "-turn",
    [Player.HistoryPhase] = "-phase",
  }
  scope = scope or Player.HistoryGame
  room:setPlayerMark(player, MarkEnum.FreezeHp .. suffix_mapper[scope], 1)
  room:sendLog {
    type = "#Freeze_skill_log",
    from = player.id,
  }
end

-- 解冻体力值
---@param player ServerPlayer @ 目标角色
Utility.unfreezeHp = function(player)
  local room = player.room
  if Utility.isfreezeHp(player) then
    room:sendLog {
      type = "#UnFreeze_skill_log",
      from = player.id,
    }
    room:setPlayerMark(player, MarkEnum.FreezeHp, 0)
    for _, suffix in ipairs(MarkEnum.TempMarkSuffix) do
      room:setPlayerMark(player, MarkEnum.FreezeHp .. suffix, 0)
    end
  end
end


---------------------------------------------------------------------------------------------------

--[[
  领袖技

  游戏开始时，若达成以下任意条件，本局游戏你可以获得领袖技：
  1.你为1号位角色；
  2.场上与你同势力的其他角色数不为1。

--]]

--- 领袖技标记（放在技能骨架skeleton的tags中）
Utility.LeaderSkill = "lb_leader_skill"

Fk:loadTranslationTable {
  ["lb_leader_skill"] = "游戏开始时，若达成以下任意条件，你获得领袖技：" ..
      "1.你为1号位角色；" ..
      "2.场上与你同势力的其他角色数不为1。",
}
---------------------------------------------------------------------------------------------------
--[[

  新系统 溯月技（By 拉布拉尔）

  溯月技初始包含至少两个分支选项，每轮限触发X次，X为溯月技的当前分支选项数量
  （发动的时间点以神华佗“静域”的触发为准，状态刷新和执行延时效果的流程不计入发动次数）。
  成玦：当一个溯月技于一轮内发动次数达到上限时，令此溯月技切换为成玦状态。
  切换为成玦状态时，技能拥有者选择一个分支选项保留，删除其余分支选项，执行成玦后所示的效果。
  部分溯月技的成玦效果能为溯月技增加分支选项，但无论何种成玦效果，其限制次数始终同分支选项的数量锚定。
  成玦删除分支选项的操作不会重置溯月技当前轮次的已发动次数。

--]]

--- 溯月技标记（放在技能骨架skeleton的tags中）
Utility.WaxneSkill = "lb_waxne_skill"

Fk:loadTranslationTable {
  ["lb_waxne_skill"] = "#\"<b>溯月技</b>\"：一种技能标签，初始包含至少两个分支选项，每轮限触发X次（X为溯月技的分支选项数量）<br>" ..
      "#\"<b>成玦</b>\"：未成玦的溯月技达到每轮发动次数上限后触发成玦时机，先选择唯一选项保留（删除其余选项），再执行成玦后的效果。",
  ["@[skills]lb_skill_waned"] = "已成玦",
}

-- 设置角色的溯月技项数或状态
---@param player ServerPlayer @ 目标角色
---@param skill string @ 目标技能
---@param effect integer|table? @ 传入整数以数字为项名
---@param mark string? @ 用以显示的标记名，值会被设置为effect
---@param reset boolean? @ 重置到初始状态，默认重置
Utility.setWanedEffect = function(player, skill, effect, mark, reset)
  reset = (reset == nil) and true or reset
  if reset then
    player.room:setPlayerMark(player, skill .. "_waned_skill_remove", 0)
    player.room:removeTableMark(player, "@[skills]lb_skill_waned", skill)
    if mark then
      player.room:setPlayerMark(player, mark, 0)
    end
  end
  if effect == nil then return end
  local list = {}
  if type(effect) == "table" then
    list = effect
  else
    for i = 1, effect, 1 do
      table.insert(list, i)
    end
  end
  player.room:setPlayerMark(player, skill .. "_waned_skill", list)
  if mark then
    player.room:setPlayerMark(player, mark, list)
  end
end

-- 令角色溯月技的某项失效
---@param player ServerPlayer @ 目标角色
---@param skill string @ 目标技能
---@param effect any @ 项数或项名
---@return boolean @ 是否成功生效
Utility.removeWanedEffect = function(player, skill, effect)
  return player.room:addTableMarkIfNeed(player, skill .. "_waned_skill_remove", effect)
end

-- 角色溯月技的某项是否生效
---@param player Player @ 目标角色
---@param skill string @ 目标技能
---@param effect any @ 项数或项名
Utility.isWanedEffect = function(player, skill, effect)
  return table.contains(player:getTableMark(skill .. "_waned_skill"), effect)
  and not table.contains(player:getTableMark(skill .. "_waned_skill_remove"), effect)
end

-- 获取角色溯月技的所有项
---@param player Player @ 目标角色
---@param skill string @ 目标技能
---@param ignoreNullified? boolean @ 忽略失效项，默认不忽略
Utility.getWanedEffect = function(player, skill, ignoreNullified)
  local list = {}
  for _, effect in ipairs(player:getTableMark(skill .. "_waned_skill")) do
    if not ignoreNullified or not table.contains(player:getTableMark(skill .. "_waned_skill_remove"), effect) then
      table.insert(list, effect)
    end
  end
  return list
end

-- 角色的某技能是否已成玦
---@param player Player @ 目标角色
---@param skill Skill|string @ 目标技能
---@return boolean
Utility.isWaned = function(player, skill)
  if type(skill) == "string" then
    skill = Fk.skills[skill]
    if skill == nil then return false end
  end
  return skill:getSkeleton() ~= nil and table.contains(player:getTableMark("@[skills]lb_skill_waned"), skill:getSkeleton().name)
end

-- 令角色的溯月技成玦
---@param player ServerPlayer @ 目标角色
---@param skill string @ 目标技能
---@param effect any @ 保留的项
---@return table @ 返回被移除的项，已成玦或成玦失败为空
Utility.setWaned = function(player, skill, effect)
  local list = {}
  if not Utility.isWaned(player, skill) then
    if player.room:addTableMarkIfNeed(player, "@[skills]lb_skill_waned", skill) then
      local effect_list = type(effect) ~= "table" and { effect } or effect
        for _, val in ipairs(Utility.getWanedEffect(player, skill)) do
          if not table.contains(effect_list, val) then
            Utility.removeWanedEffect(player, skill, val)
            table.insert(list, val)
          end
        end
    end
  end
  return list
end

---溯月技成玦相关，成玦的数据
---@class SkillCrescentDataSpec
---@field public target ServerPlayer @ 玩家

--- TriggerEvent
---@class Utility.SkillCrescentTriggerEvent: TriggerEvent
---@field public data Utility.SkillCrescentData
Utility.SkillCrescentTriggerEvent = TriggerEvent:subclass("SkillCrescentEvent")

--- 成玦时
---@class Utility.SkillCrescent: Utility.SkillCrescentTriggerEvent
Utility.SkillCrescent = Utility.SkillCrescentTriggerEvent:subclass("fk.SkillCrescent")


---------------------------------------------------------------------------------------------------

--[[
  视为装备【装备牌】，一般用于事件中

--]]

--不为0则显示拥有的视为装备

Fk:loadTranslationTable {
  ["@[VirtualEquip]VirtualEquip"] = "视为装备",
}


-- 已视为拥有某装备，name为空则返回所有装备
---@param player Player @ 目标角色
---@param name string?
Utility.hasVirtualEquip = function(player, name)
  local list = player:getTableMark("VirtualEquip")
  for _, suffix in ipairs(MarkEnum.TempMarkSuffix) do
    table.insertTableIfNeed(list, player:getTableMark("VirtualEquip" .. suffix))
  end
  if name then
    return table.contains(list, name)
  end
  return list
end

-- 增加视为装备
---@param player ServerPlayer
---@param name string
---@param scope? integer @ 生效的历史范围，默认为阶段
Utility.addVirtualEquip = function(player, name, scope)
  local room = player.room
  room:addSkill("#VirtualEquip_Skill")
  room:setPlayerMark(player, "@[VirtualEquip]VirtualEquip", 1)
  local suffix_mapper = {
    [Player.HistoryGame] = "",
    [Player.HistoryRound] = "-round",
    [Player.HistoryTurn] = "-turn",
    [Player.HistoryPhase] = "-phase",
  }
  scope = scope or Player.HistoryPhase
  room:addTableMarkIfNeed(player, "VirtualEquip" .. suffix_mapper[scope], name)
end

-- 移除视为装备
---@param player ServerPlayer
---@param name string
---@param scope? integer @ 移除的历史范围，默认为阶段
Utility.removeVirtualEquip = function(player, name, scope)
  local room = player.room
  local suffix_mapper = {
    [Player.HistoryGame] = "",
    [Player.HistoryRound] = "-round",
    [Player.HistoryTurn] = "-turn",
    [Player.HistoryPhase] = "-phase",
  }
  scope = scope or Player.HistoryPhase
  room:removeTableMark(player, "VirtualEquip" .. suffix_mapper[scope], name)
end


---------------------------------------------------------------------------------------------------
--背置牌（将一些手牌对拥有者不可见且不可使用或打出，除其外的所有角色可见。前官方概念，来自爻袁术）
--非背置牌的手牌称为面置牌，为背置引申的3d概念

--贴有此标记的牌在手牌中背置，离开手牌时清除。可带"-phase"、"-turn"等suffix
MarkEnum.BackCards = "@@BackCards-inhand"


-- 卡牌是否背置
---@param card integer | integer[] | Card | Card[]
Utility.isBackCards = function(card)
  local ids = Card:getIdList(card)
  for _, id in ipairs(ids) do
    local card_ = Fk:getCardById(id)
    if card_:getMark(MarkEnum.BackCards) > 0 or
        table.find(MarkEnum.CardTempMarkSuffix, function(suffix)
          return card_:getMark(MarkEnum.BackCards .. suffix) > 0
        end) then
      return true
    end
  end
  return false
end

--背置手牌
---@param player ServerPlayer @ 目标角色
---@param card integer | integer[] | Card | Card[] @ 要背置的牌
---@param scope? integer @ 背置的历史范围，默认永久
Utility.backCards = function(player, card, scope)
  player.room:addSkill("#BackCards")
  local suffix_mapper = {
    [Player.HistoryGame] = "",
    [Player.HistoryRound] = "-round",
    [Player.HistoryTurn] = "-turn",
    [Player.HistoryPhase] = "-phase",
  }
  scope = scope or Player.HistoryGame
  local ids = Card:getIdList(card)
  ids = table.filter(ids, function(id)
    return table.contains(player:getCardIds("h"), id)
  end)
  if #ids > 0 then
    for _, id in ipairs(ids) do
      player.room:setCardMark(Fk:getCardById(id), MarkEnum.BackCards .. suffix_mapper[scope], 1)
    end
  end
end

--面置手牌
---@param player ServerPlayer @ 目标角色
---@param card integer | integer[] | Card | Card[] @ 要面置的牌
Utility.faceupCards = function(player, card)
  local room = player.room
  local ids = Card:getIdList(card)
  ids = table.filter(ids, function(id)
    return table.contains(player:getCardIds("h"), id)
  end)
  if #ids > 0 then
    for _, id in ipairs(ids) do
      room:setCardMark(Fk:getCardById(id), MarkEnum.BackCards, 0)
      for _, suffix in ipairs(MarkEnum.CardTempMarkSuffix) do
        room:setCardMark(Fk:getCardById(id), MarkEnum.BackCards .. suffix, 0)
      end
    end
  end
end

--获得一名角色的背置手牌id列表
---@param player ServerPlayer | Player @ 目标角色
---@return integer[]
Utility.getBackCards = function(player)
  return table.filter(player:getCardIds("h"), function(id)
    if Fk:getCardById(id):getMark(MarkEnum.BackCards) > 0 then
      return true
    end
    if table.find(MarkEnum.CardTempMarkSuffix, function(suffix)
          return Fk:getCardById(id):getMark(MarkEnum.BackCards .. suffix) > 0
        end) then
      return true
    end
    return false
  end)
end

---@class AskToCardsParams: AskToUseActiveSkillParams
---@field skip? boolean @ 是否跳过背置，默认不跳过

--- 询问一名玩家背置若干张手牌，返回选择的牌。
--- 与本体askToCards类似
---@param player ServerPlayer @ 要询问的玩家
---@param params AskToCardsParams @ 各种变量
---@return integer[] @ 选择的牌的id列表，可能是空的
Utility.askToBackCards = function(player, params)
  local room = player.room
  room:addSkill("#BackCards")
  local cancelable = params.cancelable or params.cancelable == nil
  local cards = table.filter(player:getCardIds("h"), function(id)
    return not Utility.isBackCards(id)
  end)
  if #cards == 0 then return {} end
  local ret = room:askToCards(player, {
    min_num = params.min_num,
    max_num = params.max_num,
    include_equip = false,
    skill_name = params.skill_name,
    pattern = tostring(Exppattern { id = cards }),
    prompt = params.prompt,
    cancelable = cancelable,
  })
  if not cancelable and #ret == 0 then
    ret = table.random(cards, params.min_num)
  end
  if #ret > 0 and (params.skip or params.skip == nil) then
    Utility.backCards(player, ret)
  end
  return ret
end

--翻面手牌（背置牌面置，面置牌背置）
---@param player ServerPlayer @ 目标角色
---@param card integer | integer[] | Card | Card[] @ 要翻面的牌
---@param scope? integer @ 背置的历史范围，默认永久
Utility.TurnOverBackCards = function(player, card, scope)
  player.room:addSkill("#BackCards")
  local suffix_mapper = {
    [Player.HistoryGame] = "",
    [Player.HistoryRound] = "-round",
    [Player.HistoryTurn] = "-turn",
    [Player.HistoryPhase] = "-phase",
  }
  scope = scope or Player.HistoryGame
  local ids = Card:getIdList(card)
  ids = table.filter(ids, function(id)
    return table.contains(player:getCardIds("h"), id)
  end)
  if #ids > 0 then
    for _, id in ipairs(ids) do
      local card_ = Fk:getCardById(id)
      if Utility.isBackCards(id) then
        player.room:setCardMark(card_, MarkEnum.BackCards, 0)
        for _, suffix in ipairs(MarkEnum.CardTempMarkSuffix) do
          player.room:setCardMark(card_, MarkEnum.BackCards .. suffix, 0)
        end
      else
        player.room:setCardMark(card_, MarkEnum.BackCards .. suffix_mapper[scope], 1)
      end
    end
  end
end

--- 询问一名玩家翻面若干张手牌（背置牌面置，面置牌背置），返回选择的牌。
--- 与本体askToCards类似
---@param player ServerPlayer @ 要询问的玩家
---@param params AskToCardsParams @ 各种变量
---@return integer[] @ 选择的牌的id列表，可能是空的
Utility.askToTurnOverBackCards = function(player, params)
  local room = player.room
  local cancelable = params.cancelable or params.cancelable == nil
  local cards = player:getCardIds("h")
  if #cards == 0 then return {} end
  local ret = room:askToCards(player, {
    min_num = params.min_num,
    max_num = params.max_num,
    include_equip = false,
    skill_name = params.skill_name,
    pattern = tostring(Exppattern { id = cards }),
    prompt = params.prompt,
    cancelable = cancelable,
  })
  if not cancelable and #ret == 0 then
    ret = table.random(cards, params.min_num)
  end
  if #ret > 0 and (params.skip or params.skip == nil) then
    Utility.TurnOverBackCards(player, ret)
  end
  return ret
end


return Utility
